#pragma once

class CWindow;
class Mesh;
class ModelBase;
class ADTModel;
class Texture;
class M2Model;
class SkySphere;

class D3DManager : public Singleton<D3DManager>
{
	D3DPRESENT_PARAMETERS m_params;
	IDirect3D9* m_driver;
	IDirect3DDevice9* m_device;
	CWindow* m_pWindow;
	Mutex m_meshMutex;
	std::set<ModelBase*> m_meshes;
	std::map<ui32, ADTModel*> m_adts;
	ui32 m_lastUpdate;
	ui32 m_renders;
	ApplicationState m_appState;
	D3DXMATRIX m_matProj;
	Texture* m_cursor;
	SkySphere* mSkySphere;

	std::set<ADTModel*> m_changed;
	std::set<M2Model*> m_mdxModels;
	Mutex m_changeLock;
	Mutex m_renderLock;

	TaskManager mTaskMgr;

	std::vector<MODF> m_modfRendered; // we dont want to render the same wmo multiple times

	bool m_deviceLost;

	ui32 m_milliseconds;

	void checkCaps();
	void onDeviceLost();
	void resetDevice();
public:
	D3DManager(CWindow* pWindow);
	void Render();
	void OnMouseClick(Vertex2F pos, bool up, bool left);

	ui32 GetSeconds() { return m_milliseconds / 1000; }
	ui32 GetMilliseconds() { return m_milliseconds; }

	bool ShouldRender(MODF&);

	std::set<M2Model*>::iterator GetMDXBegin() { return m_mdxModels.begin(); }
	std::set<M2Model*>::iterator GetMDXEnd() { return m_mdxModels.end(); }

	void UpdateMDXPosition(ADTModel* pBaseModel, ui32 doodad, float x, float y, float z);

	bool IsMDXValid(M2Model* pModel) { return m_mdxModels.find(pModel) != m_mdxModels.end(); }

	POINT GetSize()
	{
		POINT pt;
		pt.x = m_params.BackBufferWidth;
		pt.y = m_params.BackBufferHeight;
		return pt;
	}

	void Save();

	ADTModel* GetADT(ui32 x, ui32 z) { return m_adts[x * 100 + z]; }

	void AddChanged(ADTModel* mod)
	{
		m_changeLock.Acquire();
		if(m_changed.find(mod) == m_changed.end())
			m_changed.insert(mod);
		m_changeLock.Release();
	}

	void AddMesh(ModelBase* pMesh);
	void RemoveMesh(ModelBase* pMesh);
	void AddAdt(ADTModel* m_adt, ui32 x, ui32 y) { m_renderLock.Acquire(); m_adts[x * 100 + y] = m_adt; m_renderLock.Release(); }
	void ChangeHeight(Vertex3F pos, float radius, float amount, ui32 type);
	void FlattenHeight(Vertex3F pos, float radius, float amount, ui32 type);
	void BlurHeight(Vertex3F pos, float radius, float amount, ui32 type);
	void ChangeTexture(Vertex3F pos, float radius, float amount, ui32 type, Texture* tex, ui32 brush);
	void ChangeMCCV(ui8 a, ui8 r, ui8 g, ui8 b, Vertex3F point, float radius);
	void ToggleHole(Vertex3F pos);
	void DrawShadow(Vertex3F pos, float radius, bool on);

	TaskManager& getTaskMgr() { return mTaskMgr; }

	ApplicationState GetAppState() { return m_appState; }
	void SetAppState(ApplicationState state) { m_appState = state; }

	IDirect3DDevice9* operator->()
	{
		return m_device;
	}

	operator IDirect3DDevice9*()
	{
		return m_device;
	}

	float GetLandHeight(float x, float z);
	bool getLandHeightFast(float x, float z, float& h);

	void RemoveAdt(ui32 iX, ui32 iZ);

	void ScreenShot();

	D3DXMATRIX& GetMatProj() { return m_matProj; }

	Vertex3F GetWorldCoords(ui32 mouseX, ui32 mouseY);

	void OnClose();

	void ChangeSize();
	void RedoNormals();
};

#define sD3D (*D3DManager::GetInstance())